

package com.hazelcast.map.impl.operation;

import com.hazelcast.map.impl.recordstore.RecordStore;
import com.hazelcast.spi.impl.NodeEngine;
import com.hazelcast.spi.impl.operationservice.OperationService;

import javax.annotation.Nullable;
import java.util.concurrent.ConcurrentMap;

import static com.hazelcast.config.EvictionPolicy.NONE;
import static com.hazelcast.config.InMemoryFormat.NATIVE;

/**
 * Forced eviction is done per record store basis. This interface is
 * the main contract to implement various forced eviction strategies.
 *
 * @see SingleRecordStoreForcedEviction
 * @see MultipleRecordStoreForcedEviction
 */
public interface ForcedEviction {

    int EVICTION_RETRY_COUNT = 5;

    double TWENTY_PERCENT = 0.2D;

    double HUNDRED_PERCENT = 1D;

    double[] EVICTION_PERCENTAGES = {TWENTY_PERCENT, HUNDRED_PERCENT};

    ForcedEviction[] EVICTION_STRATEGIES = {new SingleRecordStoreForcedEviction(), new MultipleRecordStoreForcedEviction()};

    static void runWithForcedEvictionStrategies(MapOperation operation) {
        for (double evictionPercentage : EVICTION_PERCENTAGES) {
            for (ForcedEviction evictionStrategy : EVICTION_STRATEGIES) {
                if (evictionStrategy.forceEvictAndRun(operation, evictionPercentage)) {
                    return;
                }
            }
        }
    }

    /**
     * Used for {@link com.hazelcast.map.impl.operation.steps.engine.Step}
     */
    static void runStepWithForcedEvictionStrategies(MapOperation mapOperation, Runnable runnable) {
        for (double evictionPercentage : EVICTION_PERCENTAGES) {
            for (ForcedEviction evictionStrategy : EVICTION_STRATEGIES) {
                if (evictionStrategy.forceEvictAndRun(mapOperation, evictionPercentage, runnable)) {
                    return;
                }
            }
        }
    }

    /**
     * @return {@code true} if supplied record store is valid
     * to apply forced eviction, otherwise return {@code false}
     */
    static boolean isValid(RecordStore recordStore) {
        return recordStore != null && recordStore.getInMemoryFormat() == NATIVE && recordStore.getEvictionPolicy() != NONE && recordStore.size() > 0;
    }

    /**
     * First does forced eviction by deleting a percentage
     * of entries then tries to run provided map operation.
     *
     * @param mapOperation       the map operation which got Native OOME during its run
     * @param evictionPercentage percentage of the entries to evict from record store.
     * @return {@code true} if run is succeeded after forced eviction,
     * otherwise return {@code false}
     * @throws com.hazelcast.memory.NativeOutOfMemoryError
     */
    boolean forceEvictAndRun(MapOperation mapOperation, double evictionPercentage);

    /**
     * First does forced eviction by deleting a percentage
     * of entries then tries to run provided map operation.
     *
     * @param mapOperation       the map operation which got Native OOME during its run
     * @param evictionPercentage percentage of the entries to evict from record store.
     * @param runnable           runnable to re-run execution
     *                           after forced eviction, when null mapOperation is run
     * @return {@code true} if run is succeeded after forced eviction,
     * otherwise return {@code false}
     * @throws com.hazelcast.memory.NativeOutOfMemoryError
     */
    boolean forceEvictAndRun(MapOperation mapOperation, double evictionPercentage, @Nullable Runnable runnable);

    default int mod(MapOperation mapOperation, int threadCount) {
        return mapOperation.getPartitionId() % threadCount;
    }

    default int threadCount(MapOperation mapOperation) {
        NodeEngine nodeEngine = mapOperation.getNodeEngine();
        OperationService operationService = nodeEngine.getOperationService();
        return operationService.getPartitionThreadCount();
    }

    default int numberOfPartitions(MapOperation mapOperation) {
        NodeEngine nodeEngine = mapOperation.getNodeEngine();
        return nodeEngine.getPartitionService().getPartitionCount();
    }

    default ConcurrentMap<String, RecordStore> partitionMaps(MapOperation mapOperation, int partitionId) {
        return mapOperation.mapServiceContext.getPartitionContainer(partitionId).getMaps();
    }

    /**
     * @return 1 if evictionPercentage is 1. 1 means we are evicting
     * all data in record store and further retrying has no point, otherwise
     * return {@link #EVICTION_RETRY_COUNT}
     */
    default int retryCount(double evictionPercentage) {
        return evictionPercentage == 1D ? 1 : EVICTION_RETRY_COUNT;
    }
}
